package ifont

import (
	"bytes"
	"github.com/disintegration/imaging"
	"github.com/gogf/gf/v2/errors/gerror"
	"github.com/gogf/gf/v2/os/gfile"
	"github.com/icza/gox/imagex/colorx"
	"golang.org/x/image/font"
	"golang.org/x/image/font/opentype"
	"golang.org/x/image/math/fixed"
	"image"
	"image/color"
	"image/draw"
	"io"
	"log"
	"math"
	"os"
	"strings"
)

type (
	// IFont 字体信息
	IFont struct {
		BgkImage    image.Image           // 图片内容
		OpenFont    *opentype.Font        // 解析字体内容
		Point       image.Point           // 字体位置坐标
		FaceOptions *opentype.FaceOptions // 字体属性
		IsSep       bool                  // 文本是否分割：false否，true分割
		Sep         string                // 分割符
	}

	// ParseTextItem 解析文本字体属性
	ParseTextItem struct {
		Width  int
		Height int
		Face   font.Face
	}
)

const (
	FaceDPIDefault     = 72  // 默认分辨率
	FontSizeDefault    = 24  // 默认字体大小
	FontSizeScaleRatio = 1.5 // 字体尺寸缩放比例
	TextSepIvX         = 6   // 分割文本X坐标偏移量
)

func New() *IFont {
	return &IFont{}
}

// SetBgkImage 设置背景图
func (f *IFont) SetBgkImage(img image.Image) {
	f.BgkImage = img
}

// SetOpenFont 设置加载的字体内容
func (f *IFont) SetOpenFont(openFont *opentype.Font) {
	f.OpenFont = openFont
}

// SetPoints 设置定位
func (f *IFont) SetPoints(point image.Point) {
	f.Point = point
}

// SetFaceOptions 设置字体字面属性
func (f *IFont) SetFaceOptions(faceOptions *opentype.FaceOptions) {
	f.FaceOptions = faceOptions
}

// SetFontItem 设置字体整体属性
func (f *IFont) SetFontItem(ft IFont) {
	if ft.BgkImage != nil {
		f.SetBgkImage(ft.BgkImage)
	}
	if ft.OpenFont != nil {
		f.SetOpenFont(ft.OpenFont)
	}
	if ft.FaceOptions != nil {
		f.SetFaceOptions(ft.FaceOptions)
	}
	if ft.IsSep == true {
		f.IsSep = true
		f.Sep = ft.Sep
	}
	f.SetPoints(ft.Point)
}

// SetIsSep 设置是否分割：false否，true分割
func (f *IFont) SetIsSep(isSep bool) {
	f.IsSep = isSep
}

// SetSep 是否设置分割符，例如：价格(99.99，分割伟[]string{`99`, `99`})
func (f *IFont) SetSep(sep string) {
	f.Sep = sep
	if len(sep) > 0 {
		f.IsSep = true
	}
}

// LoadFont 加载字体文件内容
func (f *IFont) LoadFont(path string) (err error) {
	fs, err := os.Open(path)
	if err != nil {
		return gerror.Wrapf(err, `Open font file failed, %s, fontPath %s`, err.Error(), path)
	}
	defer func() {
		_ = fs.Close()
	}()

	// 读取内容
	var buffer bytes.Buffer
	if _, err = io.Copy(&buffer, fs); err != nil {
		return gerror.Wrapf(err, `Read font file failed, fontPath %s`, path)
	}

	// 转换字体文件
	f.OpenFont, err = opentype.Parse(buffer.Bytes())
	if err != nil {
		return gerror.Wrapf(err, `Parse font content failed, %s`, err.Error())
	}
	return
}

// Open 打开图片文件
func (f *IFont) Open(path string) {
	if f.BgkImage == nil {
		img, err := imaging.Open(path)
		if err != nil {
			log.Fatalf(`Open image file failed %s`, err.Error())
		}
		f.BgkImage = img
	}
}

// DrawText 图片绘制文字
func (f *IFont) DrawText(text string, colorHex string) (err error) {
	// 默认分辨率
	if f.FaceOptions.DPI == 0 {
		f.FaceOptions.DPI = FaceDPIDefault
	}

	// 解析二进制颜色
	c, err := f.ParseHexColor(colorHex)
	if err != nil {
		return err
	}

	// 获取字体宽度和高度
	imgWidth, imgHeight := f.ImgWH()
	imgWidth -= f.Point.X // 移除占位-也就定位位置

	// 解析文本属性（完整文本宽高比）
	textAttr, err := f.AdjustFontSizeToFitImage(imgWidth, imgHeight, text)
	if err != nil {
		return err
	}

	// 计算文本位置
	textX, textY := f.TextXY(textAttr.Width, textAttr.Height)

	// 是否对文本进行分割处理:false否，true分割
	if f.IsSep == false || f.Sep == "" {
		f.ImageDrawText(text, c, textX, textY, textAttr)
		return nil
	}

	// 初始化字体尺寸（完整文本计算后字体尺寸）
	fontSize := f.FaceOptions.Size
	// 文本分割
	textList := strings.Split(text, f.Sep)
	for k, txt := range textList {
		// 解析文本属性，每次自动适应文本宽度和高度，用于下一文本偏移定位的继续
		if k > 0 {
			// 解析次部位文本宽度占位计算
			txt = f.Sep + txt
		}

		// 解析分块文本长宽比属性
		textAttr, err = f.AdjustFontSizeToFitImage(imgWidth, imgHeight, txt)
		if err != nil {
			return err
		}

		// 把文字绘制到指定位置图片
		f.ImageDrawText(txt, c, textX, textY, textAttr)

		// 次文案后移坐标和尺寸
		f.FaceOptions.Size = fontSize / FontSizeScaleRatio // 同比缩放0.95
		textX += textAttr.Width + TextSepIvX               // 并位移六个像素
	}
	return nil
}

// ImageDrawText 图片绘制文本
func (f *IFont) ImageDrawText(text string, color color.Color, textX, textY int, textAttr *ParseTextItem) {

	// 把文字绘制到指定位置图片
	d := &font.Drawer{
		Dst:  f.BgkImage.(draw.Image),
		Src:  image.NewUniform(color),
		Face: textAttr.Face,
		Dot:  fixed.P(textX, textY), // 定位
	}
	// 绘制文字
	d.DrawString(text)
}

// ParseHexColor 解析二进制颜色值
func (f *IFont) ParseHexColor(colorHex string) (color.Color, error) {
	// 解析二进制颜色
	c, err := colorx.ParseHexColor(colorHex)
	if err != nil {
		return nil, gerror.Wrapf(err, `Pares font hex color failed , %s`, err.Error())
	}
	return c, nil
}

// AdjustFontSizeToFitImage 根据图片尺寸调整字体大小以适应文本
func (f *IFont) AdjustFontSizeToFitImage(imgWidth, imgHeight int, text string) (attr *ParseTextItem, err error) {
	// 初始化
	attr = &ParseTextItem{}
	// 字体默认
	if f.FaceOptions.Size == 0 {
		f.FaceOptions.Size = FontSizeDefault
	}

	// 字面量
	attr.Face, err = opentype.NewFace(f.OpenFont, f.FaceOptions)
	if err != nil {
		return nil, gerror.Wrapf(err, `Parse font face failed %s`, err.Error())
	}

	// 获取字体高宽度
	attr.Width, attr.Height = f.MetricsTextWH(text, attr.Face)

	// 文本小于图片尺寸，直接返回文本高宽度
	if attr.Width < imgWidth && attr.Height < imgHeight {
		return
	}

	// 文本超出图片尺寸，缩小字体大小
	var (
		minSize     float64 = 1                  // 最小尺寸
		maxSize             = f.FaceOptions.Size // 初始化最大尺寸
		isFinalStep bool
	)

	for {
		// 选到匹配模式
		switch isFinalStep {
		case true:
			// 最后采用递减1临近查找
			f.FaceOptions.Size -= 0.5 // 减小字体
		default:
			// 二分查找(保留1位小数)
			f.FaceOptions.Size = math.Round((minSize+maxSize)/2*10) / 10
		}

		// 重置字体属性
		attr.Face, err = opentype.NewFace(f.OpenFont, f.FaceOptions)
		if err != nil {
			return nil, gerror.Wrapf(err, `Change font parse failed %s`, err.Error())
		}
		// 再次获取字体属性
		width, height := f.MetricsTextWH(text, attr.Face)

		// 二分查找-字体字面量小于等于图片尺寸(最后一次查找之后再折中一次数据-改为临近查找)
		if width <= imgWidth && height <= imgHeight {
			// 临近查找-找出最合适的尺寸
			if !isFinalStep {
				f.FaceOptions.Size += math.Round(f.FaceOptions.Size/2*10) / 10
				isFinalStep = true
				continue
			}
			// 赋值高宽度-返回最优匹配
			attr.Width = width
			attr.Height = height
			break
		}
		maxSize = f.FaceOptions.Size
	}
	return
}

// TextXY 文本拓印位置
func (f *IFont) TextXY(textW, textH int) (x, y int) {
	// 获取图片高宽度
	imgW, imgH := f.ImgWH()
	imgW -= f.Point.X // 移除占位-也就定位位置

	// 宽度=(背景宽度 - 字体宽度) / 2
	x = (imgW - textW) / 2

	// 设定位置:乘以72然后除以96，这个查了一下资料，简单的说，字体的大小单位磅(points) 是1/72逻辑英寸，
	// 屏幕的分辨率是96DPI（96点每逻辑英寸），那么屏幕每个点就是72/96=0.75磅
	// y = imgH * 72 / 96

	// 高度居中：（背景图高度+字体高度）/ 2
	y = (imgH + textH) / 2

	// 定位X坐标
	if f.Point.X > 0 {
		x = f.Point.X
	}

	// 定位Y坐标
	if f.Point.Y > 0 {
		y = f.Point.Y
	}
	return
}

// ImgWH 图片高宽度
func (f *IFont) ImgWH() (width, height int) {
	if f.BgkImage == nil {
		return
	}

	// 高宽度
	bounds := f.BgkImage.Bounds()
	return bounds.Dx(), bounds.Dy()
}

// MetricsTextWH 通过字体度面量获取字体高宽度
func (f *IFont) MetricsTextWH(text string, face font.Face) (width, height int) {
	// 获取度量信息
	metrics := face.Metrics()
	ascent := metrics.Ascent.Ceil()
	descent := metrics.Descent.Ceil()

	// 计算高度
	height = ascent - descent

	// 一个个计算字符串长度，累计出宽度
	for _, runeValue := range text {
		bounds, _, ok := face.GlyphBounds(runeValue)
		if !ok {
			continue
		}
		width += bounds.Max.X.Ceil()
	}
	return
}

// Save 保存图片文件
func (f *IFont) Save(filename string) error {
	if dir := gfile.Dir(filename); !gfile.IsDir(dir) {
		if err := gfile.Mkdir(dir); err != nil {
			return err
		}
	}
	// 保存文件
	if err := imaging.Save(f.BgkImage, filename); err != nil {
		return gerror.Wrapf(err, `Save image failed %s`, err.Error())
	}
	return nil
}

// CenterWH 文字居中
func (f *IFont) CenterWH(text string, face font.Face) (width, height int) {
	width, height = f.MetricsTextWH(text, face)

	// 获取背景图的边界
	imgWidth, imgHeight := f.ImgWH()

	// 宽度=(背景宽度 - 字体宽度) / 2
	width = (imgWidth - width) / 2

	// 设定位置:乘以72然后除以96，这个查了一下资料，简单的说，字体的大小单位磅(points) 是1/72逻辑英寸，
	// 屏幕的分辨率是96DPI（96点每逻辑英寸），那么屏幕每个点就是72/96=0.75磅
	height = imgHeight * 72 / 96
	return
}
